#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Beaker.Services.Interfaces;
using Moq;

#endregion

namespace Beaker.OpenCube.Specs.Builders
{
	[Obsolete("Use FileSystemServiceBuilder")]
	[ExcludeFromCodeCoverage]
	public class IOServiceBuilder
		: Builder<IIOService>
	{
		private Folder _rootFolder = new Folder( string.Empty );

		public IOServiceBuilder RootSpecialFolder( Environment.SpecialFolder folder )
		{
			_rootFolder = new Folder( folder.ToString() );
			return this;
		}

		public IOServiceBuilder WithFolder( params string[] folders )
		{
			Folder folder = _rootFolder;

			foreach( string folderName in folders )
				folder = folder.AddFolder( folderName );

			return this;
		}

		public IOServiceBuilder WithFile( string name, Stream stream, params string[] folders )
		{
			Folder folder = _rootFolder;

			foreach( string folderName in folders )
				folder = folder.AddFolder( folderName );

			folder.Add( new File( name, stream ) );

			return this;
		}

		public IOServiceBuilder WithFile( string name, string textContent, params string[] folders )
		{
			var stream = new MemoryStream();
			var writer = new StreamWriter( stream );
			writer.Write( textContent );
			writer.Flush();
			stream.Flush();
			stream.Position = 0;

			return WithFile( name, stream, folders );
		}

		public IEnumerable<Tuple<string, Stream>> GetAllFiles()
		{
			return GetAllFiles( "", _rootFolder );
		}

		private IEnumerable<Tuple<string, Stream>> GetAllFiles( string relativeFolderPath, Folder folder )
		{
			foreach( File file in folder.Files )
			{
				yield return Tuple.Create(
					Path.Combine( relativeFolderPath, folder.Name, file.Name ),
					file.Content
				);
			}

			foreach( Folder subFolder in folder.Folders )
			{
				string relativeSubFolderPath = Path.Combine( relativeFolderPath, folder.Name );

				foreach( Tuple<string, Stream> subFile in GetAllFiles( relativeSubFolderPath, subFolder ) )
					yield return subFile;
			}
		}

		protected override IIOService OnBuild()
		{
			var mock = new Mock<IIOService>();

			// Get Special Folder
			mock.Setup( s => s.GetSpecialFolderPath( It.IsAny<Environment.SpecialFolder>() ) )
				.Returns( ( Environment.SpecialFolder f ) => f.ToString() );

			// Get Directories
			mock.Setup( s => s.GetFolders( It.IsAny<string>() ) )
				.Returns( ( string p ) =>
				{
					return GetFolder( p ).Folders.Select( f => f.ToString() );
				} );

			// Get Files
			mock.Setup( s => s.GetFiles( It.IsAny<string>() ) )
				.Returns( ( string p ) =>
				{
					return GetFolder( p ).Files.Select( f => f.ToString() );
				} );
			mock.Setup( s => s.GetFiles( It.IsAny<string>(), It.IsAny<string>() ) )
				.Returns( ( string p, string sf ) =>
				{
					var sb = new StringBuilder();
					sb.Append( '^' );
					foreach( char c in sf )
					{
						if( c == '*' )
							sb.Append( ".*" );
						else if( Char.IsLetterOrDigit( c ) )
							sb.Append( c );
						else
							sb.Append( "\\u" ).AppendFormat( "{0:X4}", (int)c );
					}
					sb.Append( '$' );
					var regex = new Regex( sb.ToString() );

					return GetFolder( p )
						.Files
						.Where( f => regex.Match( f.Name ).Success )
						.Select( f => f.ToString() );
				} );

			// Directory Exists
			mock.Setup( s => s.FolderExists( It.IsAny<string>() ) )
				.Returns( ( string p ) => GetFolder( p ) != null );

			// File Exists
			mock.Setup( s => s.FileExists( It.IsAny<string>() ) )
				.Returns( ( string p ) => GetFile( p ) != null );

			// File Open Read
			mock.Setup( s => s.FileOpenRead( It.IsAny<string>() ) )
				.Returns( ( string p ) => GetFile( p ).Content );

			return mock.Object;
		}

		private Folder GetFolder( string path )
		{
			string[] segments = path.Split( '\\' );

			if( _rootFolder.Name != segments[0] )
				return null;

			Folder folder = _rootFolder;
			foreach( string segment in segments.Skip( 1 ) )
			{
				IItem item;
				if( !folder.TryGetValue( segment.ToLowerInvariant(), out item ) )
					return null;

				folder = item as Folder;
				if( folder == null )
					return null;
			}

			return folder;
		}

		private File GetFile( string path )
		{
			string[] segments = path.Split( '\\' );

			if( _rootFolder.Name != segments[0] )
				return null;

			Folder folder = _rootFolder;
			File file = null;
			foreach( string segment in segments.Skip( 1 ) )
			{
				if( folder == null )
					return null;

				IItem item;
				if( !folder.TryGetValue( segment.ToLowerInvariant(), out item ) )
					return null;

				file = item as File;
				folder = item as Folder;
			}

			return file;
		}

		private interface IItem
		{
			string Name { get; set; }
			Folder Parent { get; set; }
		}

		private sealed class Folder
			: Dictionary<string, IItem>, IItem
		{
			public Folder( string name )
			{
				Name = name;
			}

			public Folder Parent { get; set; }
			public string Name { get; set; }

			public Folder AddFolder( string folderName )
			{
				IItem folder;

				if( TryGetValue( folderName.ToLowerInvariant(), out folder ) )
					return (Folder)folder;

				folder = new Folder( folderName );
				return (Folder)Add( folder );
			}

			public TItem Add<TItem>( TItem item )
				where TItem : IItem
			{
				item.Parent = this;
				this[item.Name.ToLowerInvariant()] = item;
				return item;
			}

			public IEnumerable<IItem> Children
			{
				get { return Values; }
			}

			public IEnumerable<Folder> Folders
			{
				get { return Values.OfType<Folder>(); }
			}

			public IEnumerable<File> Files
			{
				get { return Values.OfType<File>(); }
			}

			public override string ToString()
			{
				if( Parent == null )
					return Name;

				return Parent + "\\" + Name;
			}
		}

		private sealed class File
			: IItem
		{
			public File( string name, Stream content )
			{
				Name = name;
				Content = content;
			}

			public Folder Parent { get; set; }
			public string Name { get; set; }
			public Stream Content { get; set; }

			public override string ToString()
			{
				if( Parent == null )
					return Name;

				return Parent + "\\" + Name;
			}
		}
	}
}